﻿// -----------------------------------------------------------------------
// <copyright file="Column.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System.Data.Odbc;

namespace Centido.Core
{
    using System;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    ///   A column in a user-defined table.
    /// </summary>
    /// <example>
    ///   The following example shows how to add columns to a user-defined table.
    /// <code>
    ///     using System;
    ///     using Centido.Core;
    /// 
    ///     class Sample
    ///     {
    ///         static void Main()
    ///         {
    ///             var domainConfig = new DomainConfiguration()
    ///             {
    ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
    ///                 DbProviderName = "MSSQLSERVER"
    ///             };
    ///         
    ///             var domain = Domain.Build(domainConfig);
    ///         
    ///             domain.DeleteAll();
    ///             
    ///             //Adding a new table
    ///             var table = domain.AddTable("students");
    ///             
    ///             //Adding some table columns
    ///             var nameColumn = table.AddColumn("name", ColumnType.String) as StringColumn;
    ///             nameColumn.IsNotNull = true;
    ///             nameColumn.IsNotEmpty = true;
    ///             nameColumn.MaxLength = 64;
    /// 
    ///             var birthdayColumn = table.AddColumn("birthday", ColumnType.DateTime) as DateTimeColumn;
    ///             birthdayColumn.IsNotNull = true;
    /// 
    ///             //Populating the table with data
    ///             table.GrantAll();
    ///
    ///             table.InsertRow(nameColumn.Value("John Smith"), birthdayColumn.Value(DateTime.Now.AddYears(-30)));
    ///             table.InsertRow(nameColumn.Value("Mary Sue"), birthdayColumn.Value(DateTime.Now.AddYears(-27)));
    ///         }
    ///     }   
    /// </code>
    /// </example>
    public abstract class Column : IRenamable, IRemovable, IIdentifiedById, IRefreshable
    {
        /// <summary>
        ///   The parent table.
        /// </summary>
        private readonly Table _table;

        /// <summary>
        ///   The column name.
        /// </summary>
        private string _name;

        /// <summary>
        ///   The column display name.
        /// </summary>
        private string _displayName;

        /// <summary>
        ///   The column description.
        /// </summary>
        private string _description;

        /// <summary>
        ///   The column ability to hold null values.
        /// </summary>
        private bool? _isNotNull;

        /// <summary>
        ///   The column customization level.
        /// </summary>
        private int? _customizationLevel;

        /// <summary>
        ///   The column order index.
        /// </summary>
        private int? _orderIndex;

        /// <summary>
        ///   Initializes a new instance of the <see cref = "Centido.Core.Column" /> class
        ///   for the specified column id and parent table.
        /// </summary>
        /// <param name = "columnId">The column id.</param>
        /// <param name = "table">The parent table.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        internal Column(Guid columnId, Table table)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            Id = columnId;
            _table = table;
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref = "Centido.Core.Column" /> class
        ///   for the specified column id, name, display name,
        /// description, nullability, order index, customization level and parent table.
        /// </summary>
        /// <param name="columnId">The column id.</param>
        /// <param name="table">The parent table.</param>
        /// <param name="columnName">The column name.</param>
        /// <param name="displayName">The column display name.</param>
        /// <param name="description">The column description.</param>
        /// <param name="isNotNull">The column ability to hold null values.</param>
        /// <param name="orderIndex">The column order index.</param>
        /// <param name="customizationlevel">The column customization level.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        internal Column(Guid columnId, Table table,
            string columnName, string displayName,
            string description, bool isNotNull,
            int orderIndex, int customizationlevel) : this(columnId, table)
        {
            if (table == null)
                throw new ArgumentNullException("table");
            if (columnName == null)
                throw new ArgumentNullException("columnName");
            if (columnName.Trim().Length == 0)
                throw new ArgumentException("The column name cannot be empty.");

            _name = columnName.Trim().ToLower();
            _displayName = displayName;
            _description = description;
            _isNotNull = isNotNull;
            _orderIndex = orderIndex;
            _customizationLevel = customizationlevel;
        }

        /// <summary>
        ///   Gets the column type.
        /// </summary>
        /// <example>
        ///   The following example shows how to enumerate system columns types.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Enumerating system columns types
        ///             foreach(var column in table.Columns)
        ///                 Console.WriteLine("Column: name = {0}, type = {1}", column.Name, column.ColumnType);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public abstract ColumnType ColumnType { get; }

        /// <summary>
        ///   Gets the column value database type.
        /// </summary>
        /// <example>
        ///   The following example shows how to enumerate system columns database types.
        /// <code>
        /// </code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Enumerating system columns types
        ///             foreach(var column in table.Columns)
        ///                 Console.WriteLine("Column: name = {0}, db type = {1}", column.Name, column.ValueOdbcType);
        ///         }
        ///     }
        /// </example>
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Db")]
        public abstract OdbcType ValueOdbcType { get; }

        /// <summary>
        ///   Gets or sets the display name of the column.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to set this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to set this property on a deleted column.</exception>
        /// <example>
        ///   The following example shows how to get and set the display name of a table column.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Creating a new column
        ///             var column = table.AddColumn("my_column", ColumnType.String);
        ///             
        ///             //Setting the column display name
        ///             column.DisplayName = "My new column";
        /// 
        ///             //Enumerating the columns display names
        ///             foreach(var c in table.Columns)
        ///                 Console.WriteLine("Column: name = {0}, display name = {1}", c.Name, c.DisplayName);
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public string DisplayName
        {
            get { return _displayName ?? (_displayName = DbValue.GetString(
                ReadProperty(Db.Tables.ColumnList.Col.DisplayName))); }
            set
            {
                if (value != null)
                    value = value.Trim();

                if (!string.IsNullOrEmpty(_displayName) && _displayName == value) return;

                WriteProperty(Db.Tables.ColumnList.Col.DisplayName, value, OdbcType.NVarChar);

                _displayName = value;
            }
        }

        /// <summary>
        ///   Gets or sets the description of the column.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to set this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to set this property on a deleted column.</exception>
        /// <example>
        ///   The following example shows how to get and set the description of a table column.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Creating a new column
        ///             var column = table.AddColumn("my_column", ColumnType.String);
        ///             
        ///             //Setting the column description
        ///             column.Description = "The description of the column";
        /// 
        ///             //Enumerating the columns descriptions
        ///             foreach(var c in table.Columns)
        ///                 Console.WriteLine("Column: name = {0}, description = {1}", c.Name, c.Description);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public string Description
        {
            get { return _description ?? (_description = DbValue.GetString(
                ReadProperty(Db.Tables.ColumnList.Col.Description))); }
            set
            {
                if (value != null)
                    value = value.Trim();

                if (!string.IsNullOrEmpty(_description) && _description == value) return;

                WriteProperty(Db.Tables.ColumnList.Col.Description, value, OdbcType.NVarChar);

                _description = value;
            }
        }

        /// <summary>
        ///   Gets or sets the column order index.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to set this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to set this property on a deleted column.</exception>
        /// <example>
        ///   The following example shows how to display a column list ordered by the index.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Creating a new column
        ///             table.AddColumn("my_column", ColumnType.Integer);
        /// 
        ///             //Displaying a column list ordered by the index
        ///             foreach(var c in table.Columns.OrderBy(c => c.OrderIndex))
        ///                 Console.WriteLine("Column: name = {0}, index = {1}", c.Name, c.OrderIndex);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public int OrderIndex
        {
            get
            {
                if (_orderIndex == null)
                {
                    var orderIndex = DbValue.GetInteger(ReadProperty(Db.Tables.ColumnList.Col.OrderIndex))
                        .GetValueOrDefault();

                    _orderIndex = orderIndex;
                }

                return _orderIndex.Value;
            }
            set
            {
                if (_orderIndex == value) return;

                WriteProperty(Db.Tables.ColumnList.Col.OrderIndex, value, OdbcType.Int);

                _orderIndex = value;
            }
        }

        /// <summary>
        ///   Gets or sets the column ability to hold null values.
        /// </summary>
        /// <value>True if the column can hold null values; otherwise, null.</value>
        /// <remarks>The current user has to have the permission to change the schema in order to set this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to change this property on a system column.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to set this property on a deleted column.</exception>
        /// <example>
        ///   The following example shows how to set the column ability to hold null values.
        /// <code>
        ///     using System;
        ///     using System.Data.Common;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Creating a new column
        ///             var column = table.AddColumn("my_column", ColumnType.String);
        /// 
        ///             //Setting the column to be not nullabble
        ///             column.IsNotNull = true;
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        ///             
        ///             //Inserting a not null value
        ///             table.InsertRow(column.Value("A not null value"));
        /// 
        ///             Console.WriteLine("A not null value was inserted");
        /// 
        ///             //Trying to insert a null value
        ///             try
        ///             {
        ///                 table.InsertRow(column.Value(null));
        ///             }
        ///             catch(DbException)
        ///             {
        ///                 Console.WriteLine("Cannot insert a null value");
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool IsNotNull
        {
            get
            {
                return _isNotNull ?? (_isNotNull = DbValue.GetInteger(
                ReadProperty(Db.Tables.ColumnList.Col.IsNotNull)) == 1).Value; }
            set
            {
                if (_isNotNull == value) return;

                Domain.DbAccess.ExecuteNonQuery(
                    cmd =>
                    {
                        cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                            Domain.GetObjectFullName(Db.Procedures.SetColumnNullability));

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_column_id",
                            OdbcType.Binary, GetRawId()));
                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_is_not_null",
                            OdbcType.Int, value ? 1 : 0));
                    });

                _isNotNull = value;
            }
        }

        /// <summary>
        ///   Gets the customization level of the column.
        /// </summary>
        /// <remarks>The customization level determines a set of actions that can be performed upon the column.
        /// If the customization level is zero then the column is system.</remarks>
        /// <example>
        ///   The following example shows the customization levels of table columns.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Creating a new column
        ///             var column = table.AddColumn("my_column", ColumnType.String);
        ///             
        ///             //Displaying the customization level of the columns
        ///             foreach(var c in table.Columns)
        ///                 Console.WriteLine("Column: name = {0}, customization level = {1}", c.Name, c.CustomizationLevel);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public int CustomizationLevel
        {
            get
            {
                if (_customizationLevel == null)
                {
                    var customizationLevel =
                        DbValue.GetInteger(ReadProperty(Db.Tables.ColumnList.Col.CustomizationLevel))
                        .GetValueOrDefault();

                    _customizationLevel = customizationLevel;
                }

                return _customizationLevel.Value;
            }
        }

        /// <summary>
        ///   True if the column is a system column; otherwise, false.
        /// </summary>
        public bool IsSystem
        {
            get { return CustomizationLevel == 0; }
        }

        /// <summary>
        ///   Gets the parent table of the column.
        /// </summary>
        public Table Table
        {
            get { return _table; }
        }

        /// <summary>
        ///   Gets the parent domain.
        /// </summary>
        public Domain Domain
        {
            get { return Table.Domain; }
        }

        /// <summary>
        ///   Gets the database table name that stores column of this type.
        /// </summary>
        protected abstract string BackEndTableName { get; }

        /// <summary>
        ///   Gets the column id.
        /// </summary>
        public Guid Id { get; private set; }

        /// <summary>
        ///   Checks the existence of the table column.
        /// </summary>
        /// <returns>Returns true if the table column still exists; otherwise, returns false.</returns>
        public bool Exists()
        {
            var wasFound = false;

            Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format("select count(*) from {0} where {1}=?",
                        BackEndTableName,
                        Db.Tables.ColumnList.Col.ColumnId);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("id", OdbcType.Binary, GetRawId()));
                },
                result => wasFound = DbValue.GetInteger(result) != 0);

            return wasFound;
        }

        /// <summary>
        ///   Delete current table column.
        ///   Current user has to have CanChangeSchema=true in order to execute thsi function
        /// </summary>
        public void Delete()
        {
            PrepareForRemoving();

            Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?)}}",
                        Domain.GetObjectFullName(Db.Procedures.DeleteColumn));

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_column_id",
                        OdbcType.Binary, GetRawId()));
                });

            // Deleting the column from the column cache
            Table.DeleteColumnFromCache(this);
        }

        /// <summary>
        ///   Gets or sets the name of the column.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The current user has to have the permission to change the schema in order to set this property.
        /// Moreover, the current database management system must support column renaming.
        /// </para>
        /// <para>
        /// The name of the columns is always lowered and trimmed.
        /// </para></remarks>
        /// <exception cref="ArgumentNullException">Thrown when trying to set the property to null.</exception>
        /// <exception cref="ArgumentException">Thrown when trying to set the property to an empty string.</exception>
        /// <exception cref="ArgumentException">Thrown when the new name is not a valid identifier.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is a column with the same name in the same table.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current database management system doesn't support column renaming.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to set this property on a deleted column.</exception>
        /// <example>
        ///   The following example shows how to rename a column.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");             
        /// 
        ///             //Creating a new column
        ///             var column = table.AddColumn("my_column", ColumnType.String);
        /// 
        ///             //Enumerating table columns
        ///             foreach(var c in table.Columns)
        ///                 Console.WriteLine("Column: name = {0}", c.Name);
        ///             
        ///             //Checking if the dbms supports column renaming
        ///             if (domain.Dbms.ColumnRenaming)
        ///             {
        ///                 Console.WriteLine("Renaming the column");
        /// 
        ///                 column.Name = "column_new_name";
        /// 
        ///                 //Enumerating table columns
        ///                 foreach(var c in table.Columns)
        ///                     Console.WriteLine("Column: name = {0}", c.Name);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public string Name
        {
            get { return _name ?? (_name = DbValue.GetString(ReadProperty(Db.Tables.ColumnList.Col.ColumnName))); }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                if (value.Trim().Length == 0)
                    throw new ArgumentException("Column name cannot be empty.");

                value = value.Trim().ToLower();

                if (_name == value) return;

                DbRule.TestIdentifier(value);

                Domain.DbAccess.ExecuteNonQuery(
                    cmd =>
                    {
                        cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                            Domain.GetObjectFullName(Db.Procedures.RenameColumn));

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_column_id", OdbcType.Binary, GetRawId()));
                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_new_column_name", OdbcType.NVarChar, value));
                    });

                // Removing the column with the old name from the column cache
                Table.DeleteColumnFromCache(this);

                _name = value;
            }
        }

        /// <summary>
        ///   Gets the column id formatted as a byte array.
        /// </summary>
        /// <returns>Returns the column id formatted as a byte array.</returns>
        public byte[] GetRawId()
        {
            return Id.ToByteArray();
        }

        /// <summary>
        ///   Creates an empty column value.
        /// </summary>
        /// <remarks>An empty column value can be used when reading table rows.</remarks>
        /// <returns>Returns an empty column value.</returns>
        /// <example>
        ///   The following example shows how to use empty column values when reading table rows.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a new column
        ///             var column = table.AddColumn("my_column", ColumnType.String);
        ///             
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        /// 
        ///             //Inserting a value
        ///             var id = table.InsertRow(column.Value("My column value"));
        ///             
        ///             //Creating an empty column value
        ///             var inserted = column.EmptyValue();
        /// 
        ///             //Reading the column value
        ///             table.ReadRow(id, inserted);
        /// 
        ///             Console.WriteLine("Column value: {0}", inserted.GetString());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ColumnValue EmptyValue()
        {
            return new ColumnValue(this, null);
        }

        /// <summary>
        ///   Creates a column value.
        /// </summary>
        /// <remarks>This method can be used when inserting table rows.</remarks>
        /// <param name = "columnValue">The value inside the column.</param>
        /// <returns>Returns a column value.</returns>
        /// <example>
        ///   The following example shows how to create column values when inserting table rows.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Adding two columns
        ///             var firstColumn = table.AddColumn("first_column", ColumnType.String);
        ///             var secondColumn = table.AddColumn("second_column", ColumnType.Integer);
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        /// 
        ///             //Inserting a row
        ///             var id = table.InsertRow(firstColumn.Value("My value"), secondColumn.Value(1));
        /// 
        ///             //Reading inserted values
        ///             Console.WriteLine("Row: first column = {0}, second column = {1}",
        ///                 firstColumn.Read(id).GetString(),
        ///                 secondColumn.Read(id).GetInteger());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public virtual ColumnValue Value(object columnValue)
        {
            return new ColumnValue(this, columnValue);
        }


        /// <summary>
        ///   Prepares the column for removing.
        /// </summary>
        /// <remarks>A column of each type handles this operation differently.</remarks>
        protected virtual void PrepareForRemoving()
        {
        }

        /// <summary>
        ///   Reads the column property.
        /// </summary>
        /// <param name = "propertyName">The property name.</param>
        /// <returns>Returns the property value.</returns>
        protected object ReadProperty(string propertyName)
        {
            if (propertyName == null)
                throw new ArgumentNullException("propertyName");
            if (propertyName.Trim().Length == 0)
                throw new ArgumentException("Property name cannot be empty.");

            object resultValue = null;

            _table.Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0} from {1} where {2}=? and {3}=?",
                        propertyName,
                        BackEndTableName,
                        Db.Tables.ColumnList.Col.ColumnId,
                        Db.Tables.ColumnList.Col.TableId);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("column_id", OdbcType.Binary, GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, Table.GetRawId()));
                },
                result => resultValue = result);

            return resultValue;
        }

        /// <summary>
        ///   Updates the column property.
        /// </summary>
        /// <param name = "propertyName">The property name.</param>
        /// <param name = "propertyValue">The new property value.</param>
        /// <param name = "propertyType">The property database type.</param>
        protected void WriteProperty(string propertyName, object propertyValue, OdbcType propertyType)
        {
            if (propertyName == null)
                throw new ArgumentNullException("propertyName");
            if (propertyName.Trim().Length == 0)
                throw new ArgumentException("Property name cannot be empty.");

            var rowCount = _table.Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "update {0} set {1}=? where {2}=? and {3}=?",
                        BackEndTableName,
                        propertyName,
                        Db.Tables.ColumnList.Col.ColumnId,
                        Db.Tables.ColumnList.Col.TableId);

                    cmd.Parameters.Add(
                        Domain.DbAccess.CreateParameter("value", propertyType, DbValue.GetDbValue(propertyValue)));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("column_id", OdbcType.Binary, GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, Table.GetRawId()));
                });

            if (rowCount < 1)
                throw new DbOperationException();
        }


        /// <summary>
        ///   Retrives the table name that corresponds to the specified data source.
        /// </summary>
        /// <param name="source">The target data source.</param>
        /// <returns>Returns the table name that corresponds to the specified data source.</returns>
        internal string GetDataSourceName(DataSource source)
        {
            switch (source)
            {
                case DataSource.UserView:
                    return Table.FullName;
                case DataSource.InternalTable:
                    return Table.FullInternalName;
                default:
                    throw new InvalidOperationException();
            }
        }


        /// <summary>
        ///   Refreshes the properties of the column.
        /// </summary>
        public virtual void Refresh()
        {
            _name = null;
            _displayName = null;
            _description = null;
            _isNotNull = null;
            _customizationLevel = null;
            _orderIndex = null;
        }
    }
}